import re, httplib, urllib, os, sys
from nPyxie import *
import xml.dom.minidom as minidom
import multipart
import config
import shutil

debug = True
config = config.boxConfig()

def uploadProgress(setProgress=None):
	if setProgress:multipart.progress = setProgress
	return multipart.progress

class xmlHandler:
	def __init__(self):
		pass
	
	def nodeValue(self, xmlString, node):
		RE_nodeValue = re.compile('<'+node+'>(?P<value>[\s\S]+)</'+node+'>')
		temp = RE_nodeValue.search(xmlString)
		if temp: return temp.group("value")
		# if RE_nodeValue.search(xmlString): return RE_nodeValue.search(xmlString).group('value')
		return None
	
	def nodeAtrib(self, xmlString, node, value):
		if re.search('<'+node+'>[\s\S]++</'+node+'>',xmlString):
			nodeStr1 = re.search('<'+node+'>[\s\S]+</'+node+'>',xmlString).group(0)
		if nodeStr1: nodeStr2 = nodeStr1.replace( self.nodeValue(xmlString, node), value )
		if nodeStr2: return xmlString.replace(nodeStr1, nodeStr2)

class boxHandler:
	cancel = 0
	def __init__(self):
		""" Local XML and var's init """
		global percentage; percentage = 0
		self.userInfo= {'sid': '', 'login': '', 'email': '', 'access_id': '', 'user_id': '', 'space_amount': '', 'space_used': ''}
		self.registerXMLPattern = '<xml><action>register</action><data><email>%s</email><password>%s</password></data></xml>'
		self.loginXMLPattern = '<xml>\n<action>\nauthorization\n</action>\n<login>\n%s\n</login>\n<password>\n%s\n</password>\n</xml>'
		self.flXMLPattern = '<xml>\n<action>\naccount_tree\n</action>\n<folder_id>\n%s\n</folder_id>\n<one_level>%s</one_level>\n</xml>'
		self.cfXMLPattern = '<xml><action>create_folder</action><data><folder><parent_id>%s</parent_id><name>%s</name><sharing><share>%s</share><p_comments>%s</p_comments><a_password>%s</a_password><send_to>%s</send_to><e_message>%s</e_message><type_id>%s</type_id></sharing></folder></data></xml>'
		self.mfXMLPattern = "<xml><action>move</action><data><target>%s</target><item_id>%s</item_id><destination_id>%s</destination_id></data></xml>"
		self.rfXMLPattern = "<xml><action>rename</action><data><target>%s</target><item_id>%s</item_id><new_name>%s</new_name></data></xml>"
		self.dfXMLPattern = "<xml><action>delete</action><data><target>%s</target><item_id>%s</item_id></data></xml>"
		self.ulXMLPattern = "<xml><file>%s</file></xml>"
		self.ulHTMLPattern = """<form action=%s enctype="multipart/form-data" method="POST">
									<input type="file" name="new_file1" />
									<input type="hidden" name="location" value="0" /> 
									<input type="submit" name="upload_files" value="Upload File" />
								</form>"""
		self.host = 'www.box.net'
		self.port = 80
		self.xmlH = xmlHandler()
		
	def postData(self, path, XML):
		# will we be using HTTP or SSL over HTTP?
		if config.get_goption(5) == 1: 
			c = httplib.HTTPSConnection(self.host)
			if debug: print '[API] Using secure connection...'
		else: 
			c = httplib.HTTPConnection(self.host)
			if debug: print '[API] Not using secure connection...'
		c.request("POST", path, XML)
		return c.getresponse()
	
	def register(self, email, passwd):
		registerXML = self.registerXMLPattern % (email, passwd)
		r1 = self.postData("/ping/register", registerXML)
		if debug: print "* %s %s *" % (r1.status, r1.reason)
		data = r1.read()
		xT = String2xTree(data)
		xT_nodes = ElementTypeNames(xT.Children())
		if debug: print "Status: %s" % self.xmlH.nodeValue(data, 'status')
		
	def login(self, user, passwd):
		""" This will handle the login procedure and store user info """
		self.user = user
		self.passwd = passwd
		self.loginXML = self.loginXMLPattern % (self.user,self.passwd)
		r1 = self.postData("/ping", self.loginXML)
		if debug: print "* %s %s *" % (r1.status, r1.reason)
		data = r1.read()
		xT = String2xTree(data)
		xT_nodes = ElementTypeNames(xT.Children())
		if debug: print "Status: %s" % self.xmlH.nodeValue(data, 'status')
		# Fetch user info
		if 'user' in xT_nodes:
			for key in self.userInfo.keys():
				self.userInfo[key] = self.xmlH.nodeValue(data, key)
			if debug: print self.userInfo
			return True
		return False

	def fileList(self, folder_id='0', one_level=''):
		""" Returns files on the box account.
			If folder_id is omitted, the default is "0". 
			If one_level is omitted, then the entire file tree from folder_id down will be returned. """
		self.flXML = self.flXMLPattern % (folder_id, one_level)
		r1 = self.postData("/ping/"+self.userInfo['sid'], self.flXML)
		if debug: print "* %s %s *" % (r1.status, r1.reason)
		data = r1.read()
		if debug: print "Status: %s" % self.xmlH.nodeValue(data, 'status')
		return data
		
	def parseFileList(self, data=None):
		if data==None: data = self.fileList()
		dom1 = minidom.parseString(data)
		return [BoxFile(x.attributes, x.parentNode.parentNode.attributes['id'].value) for x in dom1.getElementsByTagName("file")]

	def parseFolderList(self, data=None):
		if data==None: data = self.fileList()
		dom1 = minidom.parseString(data)
		folder_list = []
		for x in dom1.getElementsByTagName("folder"):
			parent = 'na'
			if x.parentNode.parentNode.hasAttributes(): parent = x.parentNode.parentNode.attributes['id'].value
			folder_list.append(BoxFolder(x.attributes, parent))
		folder_list
		return folder_list
		
	def refresh_fileList(self, folder_id='0'):
		""" refresh files for given folder """
		one_level = '1'
		data = self.fileList(folder_id, one_level)
		return self.parseFileList(data)

	def get_folders_from(self, fid=None, folderList=None):
		if not fid: return
		if not folderList:
			folderList = self.parseFolderList()
		folders = []
		for f in folderList:
			if f.parent_id==fid: folders.append(f.name)
		return folders

	def get_files_from(self, fid=None, fileList=None):
		if not fid: return
		if not fileList:
			fileList = self.parseFileList()
		files = []
		for f in fileList:
			if f.parent_id==fid: files.append(f.name)
		return files

	def listShared(self, data=None):
		if data==None: data = self.fileList()
		folder_list = self.parseFolderList(data)
		file_list = self.parseFileList(data)
		shared_list = []
		for f in folder_list:
			if f.shared and len(f.shared_link)>0: shared_list.append(f)
		for f in file_list:
			if f.shared and len(f.shared_link)>0: shared_list.append(f)
		return shared_list
		
	def createFolder(self, parent_id='',name='',send_to='',share='ignore',p_comments='1',a_password='',e_message='',type_id=''):
		""" This will create a folder in the given parent folder id or change its 'share' flag.
			parent_id, name and send_to are needed
			Possible server replies:
			Success:
				create_ok (followed by info for created folder)
			Errors:
				no_parent_folder
				error_folder_name
				s_folder_exists - This means the folder already exists. 
								If the share field is set differently than 
								in the original request to create the folder, 
								the share field will be updated.
			"""
		if share=='share' and send_to==None:
			if debug: print '[API]* Error *\nYou have to provide a mail address.'
			return
		self.cfXML = self.cfXMLPattern % (parent_id,name,share,p_comments,a_password,send_to,e_message,type_id)
		r1 = self.postData("/ping/"+self.userInfo['sid'], self.cfXML)
		data = r1.read()
		if debug: print "Status: %s" % self.xmlH.nodeValue(data, 'status')
		return data
		
	def uploadFile(self, filepaths=None, location="0"):
		"""
		filepaths is a dictionary whose keys are the names of the files you wish them to have,
		and the values are the filepath in your computer.
		location is a directory_id.
		"""
		temp = zip(("new_file%s" % i for i in xrange(1,1+len(filepaths) ) ), filepaths.keys(), filepaths.values() )
		data = multipart.post_multipart("www.box.net","/ping/upload/" + self.userInfo['sid'],[("location",location)], temp)
		if debug:
			if not data=="Canceled":
				print "Status: %s" % self.xmlH.nodeValue(data, 'status')
			else:
				print "Status: Upload Canceled!"
		return True
	
	def downloadMultiple(self, dict_id=None, destination=None):
		""" Bridge to downloadFile to download multiple files at once.
			dict_id: dictionary containing file_id's
		"""
		if not dict_id:
			print '[API] * downloadMultiple * You must provide argument'
			return
		global download_pos, download_total, working_file
		download_pos = 0
		download_total = len(dict_id)
		for ID in dict_id:
			self.downloadFile(ID, destination)
			download_pos += 1
		print '[API] * downloadMultiple * %s file(s) downloaded with success' % download_pos
		
	def downloadFile(self, file_id=None, destination=None):
		""" Downloads the given file.
			file_id: id number of the file to be downloaded
			(modified version of: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/83208)
		"""
		global percentage, working_file, download_total
		if self.cancel:
			self.cancel = 0
			if debug: print "downloadFile canceled!"
			return
		loop = 1
		existSize = 0
		myUrlclass = myURLOpener()
		webPage = myUrlclass.open("http://www.box.net/ping/download/%s/%s" % (file_id, self.userInfo['sid']))
		fSize = int(webPage.headers['content-length'])
		fName = eval(webPage.headers['content-disposition'].split('=')[1])
		working_file = fName
		# check if we provided a destination path
		if destination:
			# check if it exists
			if not os.path.exists(destination):
				os.mkdir(destination)
			location = os.path.join(destination, fName)
		else:
			location = os.path.join(config.get_goption(6),fName)
		if os.path.exists(fName):
			outputFile = open(location,"ab")
			existSize = os.path.getsize(fName)
			#If the file exists, then only download the remainder
			myUrlclass.addheader("Range","bytes=%s-" % (existSize))
		else:
			outputFile = open(location,"wb")
		#If the file exists, but we already have the whole thing, don't download again
		if config.get_goption(7) == 0:
			if fSize == existSize:
				loop = 0
				if debug: print "File already downloaded"
		numBytes = 0
		percentage='0.0'
		while loop:
			if self.cancel:
				self.cancel = 0
				if debug: print "downloadFile canceled!"
				return
			data = webPage.read(8192)
			if not data:
				break
			outputFile.write(data)
			numBytes = numBytes + len(data)
			self.percentage = str(numBytes * 100.0 / fSize)[:5]
			if debug: print self.percentage
			percentage = self.percentage

		webPage.close()
		outputFile.close()
		#for k,v in webPage.headers.items():
		#	print k, "=",v
		if debug: print "copied", numBytes, "bytes from", webPage.url

	def downloadPos(self):
		global download_pos
		return download_pos
		
	def downloadTotal(self):
		global download_total
		return download_total
		
	def progress(self):
		global percentage
		return float(percentage)
		
	def move(self, target=None, item_id=None, destination_id=None):
		""" Moves file/folder to given folder.
			All arguments must be defined.
			target: 'file' or 'folder'
			item_id: id number of the file/folder to be moved
			destination_id: id of the folder where the file/folder should be moved
		"""
		if not target or not item_id or destination_id==None:
			if debug: print "[API]* Error * \nArgument(s) missing.Must provide all three."
			return
		self.mfXML = self.mfXMLPattern % (target,item_id,destination_id)
		r1 = self.postData("/ping/"+self.userInfo['sid'], self.mfXML)
		data = r1.read()
		if debug: print "Status: %s" % self.xmlH.nodeValue(data, 'status')
		return data
	
	def rename(self, target=None, item_id=None, new_name=None):
		""" Renames a file/folder.
			All arguments must be defined.
			target: 'file' or 'folder'
			item_id: id number of the file/folder to be renamed
			new_name: new name for file/folder
		"""
		if not target or not item_id or not new_name:
			if debug: print "[API]* Error* \nArgument(s) missing.Must provide all three."
			return
		self.rfXML = self.rfXMLPattern % (target,item_id,new_name)
		r1 = self.postData("/ping/"+self.userInfo['sid'], self.rfXML)
		data = r1.read()
		if debug: print "Status: %s" % self.xmlH.nodeValue(data, 'status')
		return data
	
	def delete(self, target=None, item_id=None):
		""" Deletes a file/folder.
			All arguments must be defined.
			target: 'file' or 'folder'
			item_id: id number of the file/folder to be deleted
		"""
		if not target or not item_id:
			if debug: print "[API]* Error* \nArgument(s) missing.Must provide both."
			return
		self.dfXML = self.dfXMLPattern % (target,item_id)
		r1 = self.postData("/ping/"+self.userInfo['sid'], self.dfXML)
		data = r1.read()
		if debug: print "Status: %s" % self.xmlH.nodeValue(data, 'status')
		return data
		
	def getThumbnail(self, url):
		if not url:
			print '[API] * getThumbnail * You must give an url'
			return
		thumb = url.split('/')[-1]
		path = os.path.join(sys.path[0],'pixmaps','thumbs',thumb)
		try:
			open(path,'r').close()
		except:
			wfile = urllib.urlopen(url)
			wread = wfile.read()
			if "was not found" in wread:
				if debug: print "[API] *Warning* Thumbnail not found. Using default."
				path = os.path.join(sys.path[0],'pixmaps','thumbs','default_file.gif')
				return path
			lfile = open(path, 'w')
			lfile.write( wread )
			wfile.close()
			lfile.close()
		return path
	
class BoxFolder(object):
	"""Just a wrapper class for easy acess."""
	def __init__(self,nodeMap, pid):
		self.id = str(nodeMap["id"].value)
		self.name = str(nodeMap["name"].value)
		self.shared = str(nodeMap["shared"].value)
		self.shared_link = str(nodeMap["shared_link"].value)
		self.parent_id = str(pid)

class BoxFile(object):
	"""Another wrapper class for easy acess."""
	def __init__(self,nodeMap, pid):
		self.id = str(nodeMap["id"].value)
		self.name = str(nodeMap["file_name"].value)
		self.shared_link = str(nodeMap["shared_link"].value)
		self.shared = str(nodeMap["shared"].value)
		self.size = str(nodeMap["size"].value)
		self.created = str(nodeMap["created"].value)
		self.updated = str(nodeMap["updated"].value)
		self.thumbnail = str(nodeMap["thumbnail"].value)
		self.parent_id = str(pid)

class myURLOpener(urllib.FancyURLopener):
	""" Create sub-class in order to overide error 206.  This error means a
		partial file is being sent,	which is ok in this case.  Do nothing with this error.
	"""
	def http_error_206(self, url, fp, errcode, errmsg, headers, data=None):
		pass
